/*
** Copyright (C) 2011 Viens Yann
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "Singleton.hpp"
#include "GameBreakOut.hpp"
#include "Variables.hpp"
#include "ObjectDetector.hpp"
#include "Rand.hpp"
#include "Deletor.hpp"
#include "Drawer.hpp"
#include "Mover.hpp"
#include "CollisionsWithReboundsChecker.hpp"

GameBreakOut::GameBreakOut(): _bricks(NULL),
			      _borderLeft(0, 0, BORDERSIZE, camZoneHeight, 0),
			      _borderRight(camZoneWidth - BORDERSIZE, 0, BORDERSIZE, camZoneHeight, 0),
			      _borderTop(0, 0, camZoneWidth, BORDERSIZE, 0)

{
  _life = 3;
  _detector = Singleton<ObjectDetector>::instance();
}

GameBreakOut::~GameBreakOut()
{
  if (_bricks != NULL)
    deleteBricks();
}

void		GameBreakOut::start()
{
  _level = '1';
  _bricks = _loader.loadLevel(std::string("./data/levels/BreakOut_Level") + _level + ".txt", camZoneWidth, camZoneHeight);

  _borderRight.setPos(camZoneWidth - BORDERSIZE, 0);
  _borderRight.setSize(BORDERSIZE, camZoneHeight);

  _borderLeft.setPos(0, 0);
  _borderLeft.setSize(BORDERSIZE, camZoneHeight);

  _borderTop.setPos(0, 0);
  _borderTop.setSize(camZoneWidth, BORDERSIZE);


  _launched = true;
  _score = 0;
  _life = 3;
  _startDate = time(0);
  initRacket();
  _balls.push_back(new Ball(camZoneWidth / 2, 3 * (camZoneHeight / 4), rand() % 10 - 5, -5, 5, 5, 0));
}

void		GameBreakOut::stop()
{
  Deletor	deletor;

  if (_bricks != NULL)
    {
      _bricks->remove_if(deletor);
      delete _bricks;
      _bricks = NULL;
    }
  _balls.remove_if(deletor);
  _launched = false;
}

IplImage*	GameBreakOut::gameAction(IplImage* input) throw (LevelError)
{
  if (_launched)
    {
      if (_racket.getSize()._x == -1 && _racket.getSize()._y == -1)
	{
	  initRacket(input->width, input->height);
	}
      if (checkEndLevel() == false)
	{
	  checkCollisions();
	  event(input);
	  move();
	  draw(input);
	}
      return input;
    }
  else
    return input;
}

bool		GameBreakOut::checkEndLevel() throw (LevelError)
{
  if (_bricks->size() == 0)
    {
      _level += 1;
      _bricks = _loader.loadLevel(std::string("./data/levels/BreakOut_Level") + _level + ".txt", camZoneWidth, camZoneHeight);
      checkLoadedRessources();
      return true;
    }
  return false;
}

bool		GameBreakOut::checkLoadedRessources() throw (LevelError)
{
  if (_bricks == NULL)
    {
      throw LevelError();
    }
  return true;
}

void		GameBreakOut::event(IplImage* input)
{
  CvRect*	result = _detector->detect(input);

  if (result)
    {
      cvRectangle(input, cvPoint(result->x, result->y), cvPoint(result->x + result->width, result->y + result->height), cvScalar(0, 0, 255));

      if (result->x < BORDERSIZE)
	_racket.setPos(BORDERSIZE, _racket.getPos()._y);
      else if (result->x + result->width > input->width - BORDERSIZE)
	_racket.setPos(input->width - BORDERSIZE - _racket.getSize()._x, _racket.getPos()._y);
      else
	_racket.setPos(result->x + result->width / 2, _racket.getPos()._y);
    }
}

void		GameBreakOut::move()
{
  Mover		mover;

  for_each(_balls.begin(), _balls.end(), mover);
}

void		GameBreakOut::draw(IplImage* screen)
{
  if (_bricks)
    {
      Drawer	drawer(screen);
      for_each(_bricks->begin(), _bricks->end(), drawer);
    }

  if (_balls.size() != 0)
    {
      Drawer	drawer(screen);
      for_each(_balls.begin(), _balls.end(), drawer);
    }
  _racket.draw(screen);
  _borderLeft.draw(screen);
  _borderRight.draw(screen);
  _borderTop.draw(screen);
}

void		GameBreakOut::initRacket(const int width, const int height)
{
  if (width == -1 && height == -1)
    {
      _racket.setPos(-1, -1);
      _racket.setDir(-1, -1);
      _racket.setSize(-1, -1);
    }
  else
    {
      _racket.setSize(width / 5, 10);
      _racket.setPos((width / 2), height - height / 10);
      _racket.setDir((width / 2), height - height / 10);
    }
}

void		GameBreakOut::deleteBricks()
{
  Deletor	deletor;

  _bricks->remove_if(deletor);
  delete _bricks;
}

void		GameBreakOut::checkCollisions()
{
  std::list<Ball*>::iterator		itBegin = _balls.begin();
  const std::list<Ball*>::iterator	itEnd = _balls.end();

  while (itBegin != itEnd)
    {
      CollisionsWithReboundsChecker	checker(*(*itBegin), _score);

      _bricks->remove_if(checker);

      if ((*itBegin)->checkCollision(_racket))
	{
	  int	decX;

	  decX = _racket.getPos()._x + (_racket.getSize()._x / 2) - (*itBegin)->getPos()._x;

	  if (decX > 0)
	    (*itBegin)->rebounds(_racket, -decX);
	  else if (decX < 0)
	    (*itBegin)->rebounds(_racket, -decX);
	  else
	    (*itBegin)->rebounds(_racket);
	}

      if ((*itBegin)->checkCollision(_borderLeft))
	(*itBegin)->rebounds(_borderLeft);

      if ((*itBegin)->checkCollision(_borderRight))
	(*itBegin)->rebounds(_borderRight);

      if ((*itBegin)->checkCollision(_borderTop))
	(*itBegin)->rebounds(_borderTop);

      if ((*itBegin)->getPos()._y >= camZoneHeight)
	{
	  _life--;
	  if (_life > 0)
	    {
	      (*itBegin)->setPos(camZoneWidth / 2, 3 * (camZoneHeight / 4));
	      (*itBegin)->setDir(rand() % 10 + 2, -5);
	    }
	}
      ++itBegin;
    }
}
